java代码动态自定义logback日志Appender 您所在的位置:网站首页 spring xml标签中的key不存在 java代码动态自定义logback日志Appender

java代码动态自定义logback日志Appender

2024-07-12 08:09| 来源: 网络整理| 查看: 265

Java 程序中使用 Logback,需要依赖三个 jar 包,分别是 slf4j-api,logback-core,logback-classic,在 maven 项目中依赖如下:

org.slf4j slf4j-api 1.7.5 ch.qos.logback logback-core 1.0.11 ch.qos.logback logback-classic 1.0.11

Logback 在启动时,根据以下步骤寻找配置文件:

1. 在 classpath 中寻找 logback-test.xml文件;

2. 如果找不到 logback-test.xml,则在 classpath 中寻找 logback.groovy 文件;

3. 如果找不到 logback.groovy,则在 classpath 中寻找 logback.xml文件;

4. 如果上述的文件都找不到,则 logback 会使用 JDK 的 SPI 机制查找 META-INF/services/ch.qos.logback.classic.spi.Configurator 中的 logback 配置实现类, 这个实现类必须实现 Configuration 接口,使用它的实现来进行配置;

5. 如果上述操作都不成功,logback 就会使用它自带的 BasicConfigurator 来配置,并将日志输出到 console;

logback的变量作用于有三种:local,context,system

1. local 作用域在配置文件内有效;

2. context 作用域的有效范围延伸至 logger context;

3. system 作用域的范围最广,整个 JVM 内都有效;

logback 在替换变量时,首先搜索 local 变量,然后搜索 context,然后搜索 system,在spring项目中,应将变量的作用域设置为context,并交给spring控制

## application.yml文件配置 spring: profiles: active: dev application: name: msg-consumer logging: ## 自定义logback配置文件名,交给spring config: classpath:logback-custom.xml logback: ## 在配置文件中指定日志路径 logHome: logs ${pattern} UTF-8 ${logPath}-all.log ${logPath}/${LOG_NAME_PREFIX}-all.%d.%i.log 50MB 30 2GB true ${pattern} UTF-8 ${logPath}-info.log INFO ACCEPT DENY ${logPath}/${LOG_NAME_PREFIX}-info.%d.%i.log 50MB 30 2GB true ${pattern} UTF-8 ${logPath}-error.log ERROR ${logPath}/${LOG_NAME_PREFIX}-error.%d.%i.log 50MB 30 2GB true ${pattern} UTF-8 ${logPath}sql.log ${logPath}/${LOG_NAME_PREFIX}-sql.%d.%i.log 50MB 30 2GB true ${pattern} UTF-8

以上配置可满足日常开发的大部分需求,可以很方便的将info日志与error隔离开,并按照给定logger输出不同配置文件中,但存在以下问题:

1. 如果需要按照业务,将某些不同包下的日志,集中输出到指定的日志文件中,上述配置就难以实现;

2. 上述xml文件会产生大量重复配置,如appender和logger的配置,添加非常的繁琐,造成配置文件庞大;

解决方案:

1. 通过logback的SiftingAppender,通过ThreadLocal的方式动态切换,这个方案在我之前的博客中有详细实现,与业务耦合较高;

2. 在java代码中动态生成Appender,轻量,易拓展,实现代码如下;

import ch.qos.logback.classic.Level; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.filter.LevelFilter; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; import ch.qos.logback.core.ConsoleAppender; import ch.qos.logback.core.FileAppender; import ch.qos.logback.core.filter.Filter; import ch.qos.logback.core.rolling.RollingFileAppender; import ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP; import ch.qos.logback.core.rolling.TimeBasedRollingPolicy; import ch.qos.logback.core.spi.FilterReply; import ch.qos.logback.core.util.FileSize; import ch.qos.logback.core.util.OptionHelper; import cn.henry.study.common.enums.LogNameEnum; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Optional; /** * description: 自定义的日志工具类, * 需要在logback.xml,logback-spring.xml或自定义的logback-custom.xml中写入基础配置 * * @citation https://blog.csdn.net/lw656697752/article/details/84904938 * @citation https://www.cnblogs.com/leohe/p/12117183.html * @author Hlingoes * @date 2020/6/10 19:38 */ public class LoggerUtils { private static String consoleAppenderName = "serve-console"; private static String maxFileSize = "50MB"; private static String totalSizeCap = "10GB"; private static int maxHistory = 30; private static ConsoleAppender defaultConsoleAppender = null; static { Map appenderMap = allAppenders(); appenderMap.forEach((key, appender) -> { // 如果logback配置文件中,已存在窗口输出的appender,则直接使用;不存在则重新生成 if (appender instanceof ConsoleAppender) { defaultConsoleAppender = (ConsoleAppender) appender; return; } }); } /** * description: 获取自定义的logger日志,在指定日志文件logNameEnum.getLogName()中输出日志 * 日志中会包括所有线程及方法堆栈信息 * * @param logNameEnum * @param clazz * @return org.slf4j.Logger * @author Hlingoes 2020/6/10 */ public static Logger getLogger(LogNameEnum logNameEnum, Class clazz) { ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(clazz); LoggerContext loggerContext = logger.getLoggerContext(); RollingFileAppender errorAppender = createAppender(logNameEnum.getLogName(), Level.ERROR, loggerContext); RollingFileAppender infoAppender = createAppender(logNameEnum.getLogName(), Level.INFO, loggerContext); Optional consoleAppender = Optional.ofNullable(defaultConsoleAppender); ConsoleAppender realConsoleAppender = consoleAppender.orElse(createConsoleAppender(loggerContext)); // 设置不向上级打印信息 logger.setAdditive(false); logger.addAppender(errorAppender); logger.addAppender(infoAppender); logger.addAppender(realConsoleAppender); return logger; } /** * description: 创建日志文件的file appender * * @param name * @param level * @return ch.qos.logback.core.rolling.RollingFileAppender * @author Hlingoes 2020/6/10 */ private static RollingFileAppender createAppender(String name, Level level, LoggerContext loggerContext) { RollingFileAppender appender = new RollingFileAppender(); // 这里设置级别过滤器 appender.addFilter(createLevelFilter(level)); // 设置上下文,每个logger都关联到logger上下文,默认上下文名称为default。 // 但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。 appender.setContext(loggerContext); // appender的name属性 appender.setName(name.toUpperCase() + "-" + level.levelStr.toUpperCase()); // 读取logback配置文件中的属性值,设置文件名 String logPath = OptionHelper.substVars("${logPath}-" + name + "-" + level.levelStr.toLowerCase() + ".log", loggerContext); appender.setFile(logPath); appender.setAppend(true); appender.setPrudent(false); // 加入下面两个节点 appender.setRollingPolicy(createRollingPolicy(name, level, loggerContext, appender)); appender.setEncoder(createEncoder(loggerContext)); appender.start(); return appender; } /** * description: 创建窗口输入的appender * * @param * @return ch.qos.logback.core.ConsoleAppender * @author Hlingoes 2020/6/10 */ private static ConsoleAppender createConsoleAppender(LoggerContext loggerContext) { ConsoleAppender appender = new ConsoleAppender(); appender.setContext(loggerContext); appender.setName(consoleAppenderName); appender.addFilter(createLevelFilter(Level.DEBUG)); appender.setEncoder(createEncoder(loggerContext)); appender.start(); return appender; } /** * description: 设置日志的滚动策略 * * @param name * @param level * @param context * @param appender * @return ch.qos.logback.core.rolling.TimeBasedRollingPolicy * @author Hlingoes 2020/6/10 */ private static TimeBasedRollingPolicy createRollingPolicy(String name, Level level, LoggerContext context, FileAppender appender) { // 读取logback配置文件中的属性值,设置文件名 String fp = OptionHelper.substVars("${logPath}/${LOG_NAME_PREFIX}-" + name + "-" + level.levelStr.toLowerCase() + "_%d{yyyy-MM-dd}_%i.log", context); TimeBasedRollingPolicy rollingPolicyBase = new TimeBasedRollingPolicy(); // 设置上下文,每个logger都关联到logger上下文,默认上下文名称为default。 // 但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。 rollingPolicyBase.setContext(context); // 设置父节点是appender rollingPolicyBase.setParent(appender); // 设置文件名模式 rollingPolicyBase.setFileNamePattern(fp); SizeAndTimeBasedFNATP sizeAndTimeBasedFNATP = new SizeAndTimeBasedFNATP(); // 最大日志文件大小 sizeAndTimeBasedFNATP.setMaxFileSize(FileSize.valueOf(maxFileSize)); rollingPolicyBase.setTimeBasedFileNamingAndTriggeringPolicy(sizeAndTimeBasedFNATP); // 设置最大历史记录为30条 rollingPolicyBase.setMaxHistory(maxHistory); // 总大小限制 rollingPolicyBase.setTotalSizeCap(FileSize.valueOf(totalSizeCap)); rollingPolicyBase.start(); return rollingPolicyBase; } /** * description: 设置日志的输出格式 * * @param context * @return ch.qos.logback.classic.encoder.PatternLayoutEncoder * @author Hlingoes 2020/6/10 */ private static PatternLayoutEncoder createEncoder(LoggerContext context) { PatternLayoutEncoder encoder = new PatternLayoutEncoder(); // 设置上下文,每个logger都关联到logger上下文,默认上下文名称为default。 // 但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。 encoder.setContext(context); // 设置格式 String pattern = OptionHelper.substVars("${pattern}", context); encoder.setPattern(pattern); encoder.setCharset(Charset.forName("utf-8")); encoder.start(); return encoder; } /** * description: 设置打印日志的级别 * * @param level * @return ch.qos.logback.core.filter.Filter * @author Hlingoes 2020/6/10 */ private static Filter createLevelFilter(Level level) { LevelFilter levelFilter = new LevelFilter(); levelFilter.setLevel(level); levelFilter.setOnMatch(FilterReply.ACCEPT); levelFilter.setOnMismatch(FilterReply.DENY); levelFilter.start(); return levelFilter; } /** * description: 读取logback配置文件中的所有appender * * @param * @return java.util.Map> * @author Hlingoes 2020/6/10 */ private static Map allAppenders() { Map appenderMap = new HashMap(); LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); for (ch.qos.logback.classic.Logger logger : context.getLoggerList()) { for (Iterator index = logger.iteratorForAppenders(); index.hasNext(); ) { Appender appender = index.next(); appenderMap.put(appender.getName(), appender); } } return appenderMap; } } import org.apache.commons.lang3.StringUtils; /** * description: 日志枚举类,防止随意生成日志文件 * * @author Hlingoes 2020/6/10 */ public enum LogNameEnum { COMMON("common"), WEB_SERVER("webServer"), TEST("test"), ; private String logName; LogNameEnum(String fileName) { this.logName = fileName; } public String getLogName() { return logName; } public void setLogName(String logName) { this.logName = logName; } /** * description: 获取枚举类 * * @param value * @return cn.henry.study.common.enums.LogNameEnum * @author Hlingoes 2020/6/10 */ public static LogNameEnum getAwardTypeEnum(String value) { LogNameEnum[] arr = values(); for (LogNameEnum item : arr) { if (null != item && StringUtils.isNotBlank(item.logName)) { return item; } } return null; } }

 

import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.core.util.OptionHelper; import cn.henry.study.common.enums.LogNameEnum; import cn.henry.study.common.utils.LoggerUtils; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * description: * * @author Hlingoes * @date 2020/5/22 23:45 */ public class PracticeTest { private static Logger logger = LoggerFactory.getLogger(PracticeTest.class); private static Logger testLogger = LoggerUtils.getLogger(LogNameEnum.TEST, PracticeTest.class); @Test public void loggerUtilsTest() { LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); /** * * */ String oph = OptionHelper.substVars("${LOG_HOME}/${LOG_NAME_PREFIX}/test-log.log", context);     // 在日志文件common-info.log中 logger.info("logger默认配置的日志输出");     // 在日志文件common-test-info.log中 testLogger.info("testLogger#####{}####", oph); testLogger.info("testLogger看到这条信息就是info");     // 在日志文件common-test-error.log中 testLogger.error("testLogger看到这条信息就是error"); } }

 

代码是本人的git项目file-message-server的片段,留有对比参数,希望可以汇总大家的实践和想法,完善最佳实践,共勉。

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有